import sun.reflect.generics.reflectiveObjects.NotImplementedException;

public class OldNetwork {
    private static final int BITS_IN_BYTE = 8;
    private static final int OCTETS_IN_ADDRESS = 4;
    private static final int OCTET_MAX_VALUE = 255;
    private int maskLength;
    private int[] address = new int[OCTETS_IN_ADDRESS];

    public OldNetwork(String address, int maskLength) throws InvalidIpAddressException, InvalidMaskLengthException {
        this.address = addressToInt(address);
        if ( maskLength >= 0 && maskLength <= 32 ) {
            this.maskLength = maskLength;
        } else {
            throw new InvalidMaskLengthException();
        }
    }

    public boolean contains(String address) throws InvalidIpAddressException {
        int[] current = addressToInt(address);
        int[] first = addressToInt(this.getFirstUsableAddress());
        int[] last = addressToInt(this.getLastUsableAddress());

        for ( int octet = 0; octet < OCTETS_IN_ADDRESS; octet++ ){
            if ( current[octet] < first[octet] || current[octet] > last[octet] ){
                return false;
            }
        }
        return true;
    }

    public String getAddress() {
        return addressToStr(this.address);
    }

    public String getBroadcastAddress() {
        int[] broadcast = new int[OCTETS_IN_ADDRESS];
        int reminder = OCTETS_IN_ADDRESS * BITS_IN_BYTE - maskLength;

        System.arraycopy(this.address, 0, broadcast, 0, OCTETS_IN_ADDRESS);

        for ( int index = OCTETS_IN_ADDRESS - 1; index >= 0 && reminder > 0; index-- ) {
            if ( reminder > BITS_IN_BYTE ) {
                broadcast[index] = OCTET_MAX_VALUE;
                reminder -= BITS_IN_BYTE;
            } else {
                for ( int bitIndex = 0; bitIndex < reminder; bitIndex++ ) {
                    broadcast[index] = broadcast[index] | (1 << bitIndex);
                }
                reminder = 0;
            }
        }
        return addressToStr(broadcast);
    }

    public String getFirstUsableAddress() {
        int first[] = new int[OCTETS_IN_ADDRESS];

        System.arraycopy(this.address, 0, first, 0, OCTETS_IN_ADDRESS);
        for ( int index = OCTETS_IN_ADDRESS - 1; index >= 0; index--) {
            if ( first[index] < OCTET_MAX_VALUE ) {
                first[index]++;
                break;
            }
            first[index] = 0;
        }
        return addressToStr(first);
    }

    //TODO: check if octet value can go under zero
    public String getLastUsableAddress() throws InvalidIpAddressException {
        int[] last = addressToInt(this.getBroadcastAddress());

        last[OCTETS_IN_ADDRESS - 1] -= 1;
        return addressToStr(last);
    }

    public String getMask() {
        int[] mask = new int[OCTETS_IN_ADDRESS];
        int fullOctets = this.maskLength / BITS_IN_BYTE;
        int bitsLeft = this.maskLength % BITS_IN_BYTE;
        int currentOctet;

        for ( currentOctet = 0 ; currentOctet < fullOctets; currentOctet++ ) {
            mask[currentOctet] = OCTET_MAX_VALUE;
        }
        for ( int currentBit = BITS_IN_BYTE - 1; bitsLeft > 0; bitsLeft--, currentBit-- ) {
            mask[currentOctet] += 1 << currentBit;
        }
        return addressToStr(mask);
    }

    public int getMaskLength() {
        return this.maskLength;
    }

    public OldNetwork[] getSubnets() throws InvalidIpAddressException, InvalidMaskLengthException {
        int newMaskLength = this.getMaskLength() + 1;
        OldNetwork[] subnets = new OldNetwork[2];
        int[] newAddress = new int[OCTETS_IN_ADDRESS];

        System.arraycopy(address, 0, newAddress, 0, OCTETS_IN_ADDRESS);
        newAddress[this.getMaskLength() / BITS_IN_BYTE] +=  1 << ((BITS_IN_BYTE - newMaskLength % BITS_IN_BYTE) % BITS_IN_BYTE);
        subnets[0] = new OldNetwork(this.getAddress(), newMaskLength);
        subnets[1] = new OldNetwork(addressToStr(newAddress), newMaskLength);

        return subnets;
    }

    public int getTotalHosts() {
        throw new NotImplementedException();
    }

    public boolean isPublic() {
        throw new NotImplementedException();
    }

    @Override
    public String toString() {
        return addressToStr(this.address) + '/' + this.maskLength;
    }

    private String addressToStr(int[] s) {
        int l = s.length;
        StringBuilder out = new StringBuilder();

        if ( l == 0 ) {
            return null;
        }
        out.append(s[0]);
        for ( int index = 1; index < l ; index++ )
            out.append('.').append(s[index]);
        return out.toString();
    }

    private int[] addressToInt(String address) throws InvalidIpAddressException {
        String[] stringAddress = address.split("\\.");
        int[] result = new int[stringAddress.length];

        if ( stringAddress.length != 4 ) {
            throw new InvalidIpAddressException();
        }
        for ( int index = 0; index < OCTETS_IN_ADDRESS; index++ ) {
            try {
                result[index] = Integer.valueOf(stringAddress[index]);
            } catch (NumberFormatException e) {
                throw new InvalidIpAddressException();
            }
        }
        return result;
    }

    public static void main(String[] args) throws Exception{
        OldNetwork network = new OldNetwork("192.168.254.0", 23);    // ******** ******** ******** **111111
        System.out.println(network.toString());
        System.out.println(network.getAddress());
        System.out.println(network.getMask());
        OldNetwork[] subnets = network.getSubnets();
        System.out.println(subnets[0]);
        System.out.println(subnets[1]);
        /*System.out.println(network.getFirstUsableAddress());
        System.out.println(network.getLastUsableAddress());
        System.out.println(network.getBroadcastAddress());
        System.out.println(network.contains("192.0.254.115"));*/
    }
}
